package com.abcd.thread.base;

/**
 * @program: TestMaven
 * @description: Synchronized 关键字
 * @author: Liu Xinpeng
 * @create: 2021-05-27 17:51
 **/
public class SynchronizedInfo {
    /**
     * 数据会出现不安全的情况：1数据共享(临界资源) 2多线程同时访问并修改数据
     *
     * Synchronized三种使用形式(使用场景)
     * 1 修饰普通同步方法：锁对象是当前实例对象
     * 2 修饰静态同步方法：锁对象是当前类的Class类对象
     * 3 修饰同步代码块：锁对象是Synchronized后面括号里配置的对象，这个对象可以是某个对象（object），也可以是
     * 某个类（Xxx.Class）
     *
     * 注意：
     * 。使用synchronized修饰非静态方法或者是使用synchronized修饰代码块时制定的为实例对象时，同一个类的不同对象
     * 拥有自己的锁，因此不会互相阻塞
     * 。使用synchronized修饰的为类和对象的时候，由于类和对象分别拥有自己的监视器锁，因此不会互相阻塞
     * 。使用synchronized修饰实例对象的时候，如果一个线程正在访问实例对象的一个synchronized方法时，其他线程不仅
     * 不能访问该synchronized方法，并且该对象的其他synchronized方法也不能被访问，因为一个对象只能有一个监视器锁
     * 但是其他线程可以访问该对象的非synchronized方法
     * 。线程A访问实例对象的非static synchronized方法时，线程B可以访问该实例对象的 static synchronized 方法
     * 因为前者获取的是实例对象的监视器锁，后者获取的是类对象的监视器锁两者不存在互斥关系
     */


    /**
     * 锁升级
     *
     * 锁的四种状态：无状态锁、偏向锁状态、轻量级锁状态、重量级锁级状态（级别从低到高）
     * 1。偏向锁
     * 经过大量研究，大多数情况下是不存在锁竞争，常常是一个线程多次获得同一个锁，因此如果每次都要竞争锁会增大很多没有必要的代价
     * 为了降低获取锁的代价，引入了偏向锁
     * 偏向锁的升级：当线程1访问代码块并获得锁对象时，会在java对象头和栈帧中记录偏向的锁的threadID，因为偏向锁不会主动释放锁，
     * 因此以后线程1再次获取锁的时候，需要比较当前线程的threadID和Java对象头中的threadID是否一致，如果一致（还是线程1获取锁对象）
     * 这样就不需要CAS来加锁、解锁；如果不一致（其他线程，比如线程2要竞争锁对象，而偏向锁不会主动释放因此还是存储的线程1的threadID）
     * 那么就需要查看java对象头中记录的线程1是否存活，如果没有存活，则锁对象别重制为无锁状态，其他线程（线程2）可以竞争将其设置为偏向锁；
     * 如果存活，那么立即查找该线程（线程1）的栈帧信息，如果还是需要继续持有这个锁对象，则暂停当前线程1，撤销偏向锁，升级为轻量级锁，
     * 如果线程1不再使用该锁对象，那么将锁对象状态设置为无状态锁，重新偏向新的线程
     * 偏向锁的取消：偏向锁是默认开启的，而且开始时间一般是比应用程序启动慢几秒，如果不想有这个延迟，可以用 XX:BiasedLockingStartUpDelay=0；
     * 如果不想用偏向锁，可以通过：XX:BiasedLockingStartUpDelay=0；
     *
     * 2。轻量级锁
     * 轻量级锁考虑到的是竞争锁对象的线程不多，而且线程持有锁的时间也不长的场景。以为阻塞线程需要CPU从用户状态切换到内核状态，代价比较大
     * 如果刚刚阻塞不久这个线程就被释放了，这个代价就有点得不偿失，因此有时候就干脆不阻塞这个线程，让它自选着等待锁释放
     * 轻量级锁什么时候升级为重量级锁？
     * 线程1获取轻量级锁的时候会把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间，然后使用CAS吧对象头中的内容
     * 替换成线程1存储的锁记录的地址，如果线程1复制对象头的同时（在线程1CAS之前），线程2也准备获取锁，复制了对象头到线程2的锁记录空间中，
     * 但是在现场CAS的时候发现线程1已经把对象头换了，线程2的CAS失败，那么线程2就自旋来等待线程1释放锁
     * 但是如果自旋的时间太长也不行，因为旋是会消耗CPU的，因此自旋的次数是有限的，如果线程2的自旋次数达到了，线程1还没有释放锁或者线程1还在执行
     * 这时又有一个线程3来竞争这个锁对象，那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞，防止CPU空转
     *
     * 为了避免不必要的自旋，轻量级锁膨胀为重量级锁之后就不会再降级为轻量级锁；偏向级锁升级为轻量级锁之后也不会降级为偏向级锁。
     * 总结就是锁可以升级不可以降级，但是重量级锁可以被重置为无状态锁
     */

    /**
     * 锁粗化
     * 同步块的范围应该尽可能小，仅在共享数据的实际作用域中才进行同步，这样做的目的是为了使需要同步的操作数尽可能缩小，缩短阻塞时间，如果存在竞争
     * 那么等待锁的线程也应该尽可能块的获取到锁。但是加锁解锁也需要消耗资源，如果存在一系列的连续加锁解锁的操作，可能会导致不必要的性能损耗
     * 锁粗化就是将多个连续的加锁、解锁操作连接到一起，扩展成一个范围更大的锁，避免频繁的加锁操作
     */

    /**
     * 锁消除
     * Java虚拟机在JIT编译的时候（可以简单理解为当某段代码即将第一次被编译的时候，又称为即时编译），通过对运行上下文的扫描，经过逃逸分析
     * 去除不可能存在的共享资源竞争的锁，通过这种方式消除没有必要的锁，可以节省毫无意义的请求锁时间
     */
}